package com.ruoyi.car.es.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageInfo;
import com.ruoyi.car.controller.admin.carinfo.vo.CarInfoProperties;
import com.ruoyi.car.controller.admin.carinfo.vo.CarInfoReq;
import com.ruoyi.car.controller.admin.carinfo.vo.CarInfoResp;
import com.ruoyi.car.entity.CarInfoPhoto;
import com.ruoyi.car.es.entity.EsCarInfo;
import com.ruoyi.car.es.repository.EsCarInfoRepository;
import com.ruoyi.car.mapstruct.CarInfoConvert;
import com.ruoyi.car.service.CarInfoService;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.log4j.Log4j2;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author wy
 * @Description 汽车搜索业务逻辑
 * @Date 2023/6/15 16:39
 * @Version 1.0
 */
@Log4j2
@Service
public class EsCarInfoService {

    public static final String CARINFO_INDEX="carinfo";

    @Autowired
    private EsCarInfoRepository esCarInfoRepository;

    @Autowired
    private CarInfoService carInfoService;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 必须要去配置EsConfig
     */
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     *  线程池
     */
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * 重新加载汽车表中的数据，到ES中去
     */
    public synchronized void reloadCarInfoToEs() throws IOException, InterruptedException {
        this.clearEsCarAllInfo();
        Long aLong = this.batchImportData();
        log.info("同步汽车信息到ES库中，共完成"+aLong+"笔");
    }

    /**
     * 清空es表中carinfo的索引库中的所有数据
     */
    private void clearEsCarAllInfo() throws IOException {
//        DeleteRequest  deleteRequest=new DeleteRequest(CARINFO_INDEX);
//        DeleteResponse delete = this.restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        this.esCarInfoRepository.deleteAll();
    }

    /**
     * 批量导入数据
     * @return
     */
    public Long batchImportData() throws JsonProcessingException, InterruptedException {
        //查询数据库表中所有数据
        long count = this.carInfoService.count();
        //总共多少页
        int pageCount= (int) Math.ceil(count *1.0/ IService.DEFAULT_BATCH_SIZE);
        //超过3千多笔数据,使用多线程来操作
        if(pageCount>3){
            CountDownLatch  countDownLatch=new CountDownLatch(3);
           threadBatchExecution(countDownLatch,pageCount);
           //countDownLatch=0 通知这挂起的线程，做后续的处理
           countDownLatch.await();

        }else{
            for(int i=0;i<pageCount;i++) {
                CarInfoReq carInfoReq = new CarInfoReq();
                carInfoReq.setPage(i+1);
                carInfoReq.setSize(IService.DEFAULT_BATCH_SIZE);
                PageInfo<CarInfoResp> search = this.carInfoService.search(carInfoReq);
                writeBatchEsCarInfoIndex(search.getList());
            }
        }
        return count;
    }

    /**
     * 开多线程处理
     * @param countDownLatch
     * @param pageCount 共多少页
     */
    private void threadBatchExecution(CountDownLatch countDownLatch, int pageCount) {
        long count = countDownLatch.getCount();
        // 1 2 3 4 5 6 7 8
        //线程 1 :   1  4   7            countDownLatch -1
        //线程2:    2   5   8             countDownLatch -1
        //线程3 :   3   6                  countDownLatch -1

        Map<Integer,List<Integer>> mp=new HashMap<>();
        for(int i=0;i<pageCount;i++){
            // 0%3=0   3%3=0   6%3=0
            // 1%3=1   4%3=1   7%3=1
            // 2%3=2   5%3=2   8%3=2
            int index= (int) (i% count);
            if(mp.get(index)==null){
                mp.put(index,new ArrayList<Integer>());
            }
            List<Integer> data= mp.get(index);
            data.add( i+1);
        }

        //开线程
        AtomicInteger ai=new AtomicInteger(0);
        while ( ai.getAndIncrement()<count) {
            threadPoolTaskExecutor.execute(() -> {
                try {

                    threadBatchEsCarInfo(countDownLatch,mp.get( ai.get() -1));

                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
            });
        }

    }

    /**
     * 使用多线程同步哪个页的数据
     * @param arr
     */
    private void threadBatchEsCarInfo(CountDownLatch countDownLatch,List<Integer> arr) throws JsonProcessingException {
       //自己玩自己的
        if(arr==null){
            countDownLatch.countDown();
            return;
        }

        for(int page : arr){
            CarInfoReq carInfoReq = new CarInfoReq();
            carInfoReq.setPage(page);
            carInfoReq.setSize(IService.DEFAULT_BATCH_SIZE);
            PageInfo<CarInfoResp> search = this.carInfoService.search(carInfoReq);
            writeBatchEsCarInfoIndex(search.getList());
        }

        //做完了
        countDownLatch.countDown();
    }

    /**
     * 写入数据到es中
     * @param arr
     */
    private void writeBatchEsCarInfoIndex(List<CarInfoResp> arr) throws JsonProcessingException {
        List<EsCarInfo> carInfoList=new ArrayList<>();
        for(CarInfoResp  resp: arr) {
            EsCarInfo esCarInfo = CarInfoConvert.INSTANCE.convert(resp);
            //图片字符串配置
            if(resp.getCarInfoPhotoList()!=null && resp.getCarInfoPhotoList().size()>0){
                String jsonStr=objectMapper.writeValueAsString(resp.getCarInfoPhotoList());
                esCarInfo.setCarPicture(jsonStr);
            }

            carInfoList.add(esCarInfo);
        }
        this.esCarInfoRepository.saveAll(carInfoList);
    }

    /**
     * ES搜索
     * @param req
     * @return
     */
    public TableDataInfo search(CarInfoReq req) throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest(CARINFO_INDEX);
        //2、bool条件组装
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //得到一个 and 条件集合
        List<QueryBuilder> mustList = boolQueryBuilder.must();

        //车牌号  "car_no": {   "type": "keyword"  }    TermQuery
        if(StringUtils.isNotBlank(req.getCarNo())){
            mustList.add(QueryBuilders.termQuery("car_no", req.getCarNo()));
        }
        //部门编号  dept_id": {      "type": "long"    }   TermQuery   RangeQuery
        if(Objects.nonNull(req.getDeptId())){
            mustList.add(QueryBuilders.termQuery("dept_id", req.getDeptId()));
        }

        //公司编号 componey_id {      "type": "long"    }   TermQuery   RangeQuery
        if(Objects.nonNull(req.getComponeyId())){
            mustList.add(QueryBuilders.termQuery("componey_id", req.getComponeyId()));
        }
        //客户名称 "customer_name": {  "type": "text",  "analyzer": "ik_max_word" }  MatchQuery
        if(StringUtils.isNotBlank(req.getCustomerName())){
            mustList.add(QueryBuilders.matchQuery("customer_name", req.getCustomerName()));
        }
        //contactPhone contact_phone": {   "type": "keyword"   } TermQuery
        if(StringUtils.isNotBlank(req.getContactPhone())){
            mustList.add(QueryBuilders.termQuery("contact_phone", req.getContactPhone()));
        }
        //vin   {   "type": "keyword"   } TermQuery
        if(StringUtils.isNotBlank(req.getVin())){
            mustList.add(QueryBuilders.termQuery("vin", req.getVin()));
        }
        //"new_energy_vehicles": {       "type": "keyword" }  TermQuery
        if(StringUtils.isNotBlank(req.getNewEnergyVehicles())){
            mustList.add(QueryBuilders.termQuery("new_energy_vehicles", req.getNewEnergyVehicles()));
        }

        //日期范围
        if(Objects.nonNull(req.getSearchDateType()) && req.getSearchDateType()>1 && req.getBeginTime()!=null && req.getEndTime()!=null){
            //1 c.create_time  查询 创建时间   没有，不按这个查询
            // 2 c.inspection_date 查询  年检到期时间  RangeQuery
             Date endTimeAddOneDay= DateUtils.addDays(req.getEndTime(),1);
             if(req.getSearchDateType()==2){
               mustList.add( QueryBuilders.rangeQuery("inspection_date").gte(req.getBeginTime()).lte(endTimeAddOneDay ));
             }else if(req.getSearchDateType()==3){
                 //3 c.insurance_date  保险到期时间
                 mustList.add( QueryBuilders.rangeQuery("insurance_date").gte(req.getBeginTime()).lte(endTimeAddOneDay ));
             }
        }

        //一个条件都没有,应该做全查询
        if(boolQueryBuilder.must().size()==0){
            mustList.add(QueryBuilders.matchAllQuery());
        }
        //构造查询条件
        request.source().query(boolQueryBuilder);

         //按ID排序
         request.source().sort("id", SortOrder.ASC);
         //分页
        request.source().from( (req.getPage()-1)*req.getSize());
        request.source().size(req.getSize());

        return searchResult(request);
    }

    /**
     * 查询并解析返回结果
     * @param request
     * @return
     * @throws IOException
     */
    private TableDataInfo searchResult(SearchRequest request) throws IOException {
        //5、以下都是固定的代码，可以封装，  发送dsl查询，返回查询结果对象
        SearchResponse searchResponse = this.restHighLevelClient.search(request, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();

        //总记录数
        long total=hits.getTotalHits().value;
        //当前页的记录数
        SearchHit[] searchHits = hits.getHits();
        //输出结果
        List<CarInfoResp>  arrList=new ArrayList<>();

        for(SearchHit  hit: searchHits){
            //直接拿到json数据
            String jsonStr=hit.getSourceAsString();
            EsCarInfo esCarInfo = this.objectMapper.readValue(jsonStr, EsCarInfo.class);
           // mapStruct来转换对象
            CarInfoResp carInfoResp = CarInfoConvert.INSTANCE.convert(esCarInfo);
           //自定义属性 json反序列化
            //动态扩展attributes  转换   List<Map>
            String attr=carInfoResp.getAttributes();
            if(StringUtils.isNotBlank(attr)){
                //List<Map>
                JavaType javaType=objectMapper.getTypeFactory().constructParametricType(List.class, CarInfoProperties.class);
                try {
                    List<CarInfoProperties> listMap= objectMapper.readValue(attr, javaType);
                    carInfoResp.setAttributesList(listMap);
                } catch (JsonProcessingException e) {
                    log.error(e);
                    throw new ServiceException(MessageUtils.message("json.processing.exception"));
                }
            }

            //转换图片
            String carPicture=esCarInfo.getCarPicture();
            if(StringUtils.isNotBlank(carPicture)){
                //List<Map>
                JavaType javaType=objectMapper.getTypeFactory().constructParametricType(List.class, CarInfoPhoto.class);
                try {
                    List<CarInfoPhoto> listMap= objectMapper.readValue(carPicture, javaType);
                    carInfoResp.setCarInfoPhotoList(listMap);
                } catch (JsonProcessingException e) {
                    log.error(e);
                    throw new ServiceException(MessageUtils.message("json.processing.exception"));
                }
            }

            arrList.add(carInfoResp);
        }

        //输出结果
        TableDataInfo tableDataInfo=new TableDataInfo(arrList,total);
        return tableDataInfo;
    }
}
